package com.boot2.core.reflect;

import com.boot2.core.utils.StringUtils;
import org.apache.commons.collections.CollectionUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 目录，jar包，类扫描器
 * 
 * @author zhangweilin
 * @date 2015年12月1日 下午5:00:58
 * @version V1.0
 * @description
 */
public class ClassPathPackageScanner
{
    private String basePackage;
    private ClassLoader classLoader;

    /**
     * Construct an instance and specify the base package it should scan.
     * 
     * @param basePackage
     *            The base package to scan.
     */
    public ClassPathPackageScanner(String basePackage)
    {
        this.basePackage = basePackage;
        this.classLoader = getClass().getClassLoader();

    }

    /**
     * Construct an instance with base package and class loader.
     * 
     * @param basePackage
     *            The base package to scan.
     * @param cl
     *            Use this class load to locate the package.
     */
    // public ClassPathPackageScanner(String basePackage, ClassLoader cl)
    // {
    // this.basePackage = basePackage;
    // this.classLoader = cl;
    // }

    /**
     * Get all fully qualified names located in the specified package and its
     * sub-package.
     *
     * @return A list of fully qualified names.
     * @throws IOException
     */
    public static List<String> getFullyQualifiedClassNameList(String basePackage) throws IOException
    {
        System.out.println("开始扫描包{}下的所有类: " + basePackage);
        return doScan(basePackage, new ArrayList<String>());
    }

    /**
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClassesByPackageName(String packageName)
    {
        List<Class<?>> classes = new ArrayList<>();
        try
        {
            List<String> classNameList = ClassPathPackageScanner.getFullyQualifiedClassNameList(packageName);
            for (String className : classNameList)
            {
                Class<?> clazz = Class.forName(className);
                classes.add(clazz);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return classes;
    }

    /**
     * Actually perform the scanning procedure.
     *
     * @param basePackage
     * @param nameList
     *            A list to contain the result.
     * @return A list of fully qualified names.
     * @throws IOException
     */
    private static List<String> doScan(String basePackage, List<String> nameList) throws IOException
    {
        // replace dots with splashes
        String splashPath = StringUtils.dotToSplash(basePackage, "xml", "ftl");
        // System.out.println("splashPath: " + splashPath);
        // get file path
        URL url = ClassPathPackageScanner.class.getClassLoader().getResource(splashPath);
        String filePath = StringUtils.getRootPath(url);

        // Get classes in that package.
        // If the web server unzips the jar file, then the classes will exist in
        // the form of
        // normal file in the directory.
        // If the web server does not unzip the jar file, then classes will
        // exist in jar file.
        List<String> names = null; // contains the name of the class file. e.g.,
                                   // Apple.class will be stored as "Apple"
        boolean isJarFile = false;
        if (isJarFile(filePath))
        {
            // jar file
            // System.out.println("是一个JAR包: " + filePath);

            names = readFromJarFile(filePath, splashPath);
            isJarFile = true;
        }
        else if (isIgnoreFile(filePath))
        {
            // System.out.println("是一个xml,忽略: " + filePath);
        }
        else
        {
            // directory
            // System.out.println("是一个目录: " + filePath);
            names = readFromDirectory(filePath);
        }

        if (CollectionUtils.isNotEmpty(names))
        {
            for (String name : names)
            {
                if (isClassFile(name))
                {
                    // nameList.add(basePackage + "." +
                    // StringUtil.trimExtension(name));
                    // System.out.println("name2: " + name);
                    // nameList.add(toFullyQualifiedName(name, basePackage));
                    String className = null;
                    if (isJarFile)
                    {
                        className = StringUtils.trimExtension(StringUtils.splashToDot(name, "xml", "ftl"));
                    }
                    else
                    {
                        className = toFullyQualifiedName(name, basePackage);
                    }
                    nameList.add(className);
                }
                else
                {
                    // this is a directory
                    // check this directory for more classes
                    // do recursive invocation
                    doScan(basePackage + "." + name, nameList);
                }
            }
        }
        for (String n : nameList)
        {
            System.out.println("扫描到类: " + n);
        }

        return nameList;
    }

    private static boolean isIgnoreFile(String filePath)
    {
        String[] suffixArr = { "xml", "ftl" };
        for (String suffix : suffixArr)
        {
            if (filePath.endsWith(suffix))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Convert short class name to fully qualified name. e.g., String ->
     * java.lang.String
     */
    private static String toFullyQualifiedName(String shortName, String basePackage)
    {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(StringUtils.trimExtension(shortName));

        return sb.toString();
    }

    private static List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException
    {

        System.out.println("从JAR包中读取类: {}: " + jarPath);

        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
        JarEntry entry = jarIn.getNextJarEntry();

        List<String> nameList = new ArrayList<>();
        while (null != entry)
        {
            String name = entry.getName();
            if (name.startsWith(splashedPackageName) && isClassFile(name))
            {
                nameList.add(name);
            }

            entry = jarIn.getNextJarEntry();
        }

        return nameList;
    }

    private static List<String> readFromDirectory(String path)
    {
        File file = new File(path);
        String[] names = file.list();

        if (null == names)
        {
            return null;
        }

        return Arrays.asList(names);
    }

    private static boolean isClassFile(String name)
    {
        return name.endsWith(".class");
    }

    private static boolean isJarFile(String name)
    {
        return name.endsWith(".jar");
    }

    // /**
    // * For test purpose.
    // */
    // public static void main(String[] args) throws Exception
    // {
    // ClassPathPackageScanner scan = new
    // ClassPathPackageScanner("com.mysql.jdbc");
    // scan.getFullyQualifiedClassNameList();
    // }

}
